原始题目:剑指 Offer 59 - II. 队列的最大值 (opens new window)

解题思路:

普通的队列出队入队的时间复杂度都是 O(1)O(1) 的,如果说获取最大值要遍历所有的元素,那么不符合题意,因为这样 max_value 函数的时间复杂度会变成 O(N)O(N)

如果采用单一变量来记录当前最大值的话,那么最大值出队后,无法确定下一个最大值。

理想状态是用另一个双端队列 D1D1 记录最大值。D1D1 的队头元素表示队列 Q1Q1 所有的最大值,而且 D1D1 是一个递减队列。

如何维护这个双端队列呢?

  • push_back:当一个元素 xx 入队 Q1Q1 的时候,判断 x 是否大于 D1D1 的队尾元素 aa,如果大于队尾元素,就把 aa 弹出( aa 由于 xx 的存在,绝对不会是最大值了)。循环判断,直到 D1D1 为空或者 D1D1 的队尾元素不小于 xx

    比如对于队列 11111122

    不管 11 有没有弹出,对于最大值是 22 这个事实没有影响,也就是说,如果 22 没弹出,那么就不用管 11 是否为最大。

  • pop_front:当一个元素 xx 出队 Q1Q1 的时候,判断这个 xx 是否等于 D1D1 的队头元素(即 xx 是否为队列的最大值),如果相等就把 D1D1 的队头元素出队,否则不出队。

代码:

class MaxQueue {

    Queue<Integer> queue;
    /**
     * 双端队列,维护一个单调递减的队列。
     * 当新插入一个元素时,检查双端队列的队尾,将所有小于新元素的元素弹出。
     * 因为有新元素的存在,所有之前小于新元素的元素都不会是最大值
     */
    Deque<Integer> deque;

    public MaxQueue() {
        queue = new LinkedList<>();
        deque = new LinkedList<>();
    }

    public int max_value() {
        return deque.isEmpty() ? -1 : deque.peekFirst();
    }

    public void push_back(int value) {
        queue.offer(value);
        while (!deque.isEmpty() && deque.peekLast() < value) {
            deque.pollLast();
        }
        deque.offerLast(value);
    }

    public int pop_front() {
        if(queue.isEmpty()){
            return -1;
        }
        if(!deque.isEmpty() && deque.peekFirst().equals(queue.peek())){
            deque.pollFirst();
        }
        return queue.poll();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

复杂度分析

  • 时间复杂度O(1)O(1)max_valuepush_backpop_front 方法的均摊时间复杂度均为 O(1)O(1)
  • 空间复杂度O(N)O(N):当元素个数为 NN 时,双端队列最多要保存 NN 个元素。
上次更新: 2023/10/15